package de.bensoft.jrail.client.ui.trackplan;

import java.util.ArrayList;
import java.util.List;

import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.scene.Node;
import javafx.scene.control.Slider;
import javafx.scene.control.ToggleButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.FlowPane;

import javax.inject.Inject;
import javax.inject.Singleton;

import de.bensoft.jrail.business.entity.Trackplan;
import de.bensoft.jrail.business.entity.TrackplanSegment;
import de.bensoft.jrail.business.service.TrackplanService;
import de.bensoft.jrail.client.common.Utils;
import de.bensoft.jrail.client.ui.trackplan.segments.BasicTrackSegment;
import de.bensoft.jrail.client.ui.trackplan.segments.BlockTrackSegment;
import de.bensoft.jrail.client.ui.trackplan.segments.Orientation;
import de.bensoft.jrail.client.ui.trackplan.segments.rails.CurveNorthEast;
import de.bensoft.jrail.client.ui.trackplan.segments.rails.CurveNorthWest;
import de.bensoft.jrail.client.ui.trackplan.segments.rails.CurveSouthEast;
import de.bensoft.jrail.client.ui.trackplan.segments.rails.CurveSouthWest;
import de.bensoft.jrail.client.ui.trackplan.segments.rails.DeflectorLeftEast;
import de.bensoft.jrail.client.ui.trackplan.segments.rails.DeflectorLeftNorth;
import de.bensoft.jrail.client.ui.trackplan.segments.rails.DeflectorLeftSouth;
import de.bensoft.jrail.client.ui.trackplan.segments.rails.DeflectorLeftWest;
import de.bensoft.jrail.client.ui.trackplan.segments.rails.DeflectorRightEast;
import de.bensoft.jrail.client.ui.trackplan.segments.rails.DeflectorRightNorth;
import de.bensoft.jrail.client.ui.trackplan.segments.rails.DeflectorRightSouth;
import de.bensoft.jrail.client.ui.trackplan.segments.rails.DeflectorRightWest;
import de.bensoft.jrail.client.ui.trackplan.segments.rails.StraightHorizontal;
import de.bensoft.jrail.client.ui.trackplan.segments.rails.StraightVertical;

@Singleton
public class TrackplanController {
		
	// *****************************************************************
	// Statics
	// *****************************************************************
	private static final Double SEGMENTSIZE = 25d;
	
	public static enum Tool { SELECT, PAINT, DELETE }
	private Tool currentTool;
	public Tool getCurrentTool() { return currentTool; }
	
	@SuppressWarnings("rawtypes")
	private Class currentPaintSegment;
	
	@Inject
	TrackplanService tpService;

	@Inject
	Utils utils;
	
	// *****************************************************************
	// FXML
	// *****************************************************************
	@FXML
	FlowPane fpContent;
	
	@FXML
	Slider slSize;
	
	@FXML
	private void onSave() {
		save();
	}
	
	@FXML
	private void onRefresh() {
		refresh();
	}
	
	@FXML
	private void onTracksymbolSelected(ActionEvent ev) {
		if(ev.getSource() instanceof ToggleButton) {
			ToggleButton tb = (ToggleButton)ev.getSource();
			
			if(tb.getId() == null)
				return;
			
			// Straight
			if(tb.getId().equals("straight_horizontal"))
				currentPaintSegment = StraightHorizontal.class;
			else if(tb.getId().equals("straight_vertical"))
				currentPaintSegment = StraightVertical.class;
			// Curve
			else if(tb.getId().equals("curve_nw"))
				currentPaintSegment = CurveNorthWest.class;
			else if(tb.getId().equals("curve_ne"))
				currentPaintSegment = CurveNorthEast.class;
			else if(tb.getId().equals("curve_sw"))
				currentPaintSegment = CurveSouthWest.class;
			else if(tb.getId().equals("curve_se"))
				currentPaintSegment = CurveSouthEast.class;
			// Deflector
			else if(tb.getId().equals("defl_n"))
				currentPaintSegment = DeflectorLeftNorth.class;
			else if(tb.getId().equals("defl_s"))
				currentPaintSegment = DeflectorLeftSouth.class;
			else if(tb.getId().equals("defl_e"))
				currentPaintSegment = DeflectorLeftEast.class;
			else if(tb.getId().equals("defl_w"))
				currentPaintSegment = DeflectorLeftWest.class;
			else if(tb.getId().equals("defr_n"))
				currentPaintSegment = DeflectorRightNorth.class;
			else if(tb.getId().equals("defr_s"))
				currentPaintSegment = DeflectorRightSouth.class;
			else if(tb.getId().equals("defr_e"))
				currentPaintSegment = DeflectorRightEast.class;
			else if(tb.getId().equals("defr_w"))
				currentPaintSegment = DeflectorRightWest.class;
			
			else if(tb.getId().equals("block"))
				currentPaintSegment = BlockTrackSegment.class;
		}
	}
	
	@FXML
	private void onToolSelected(ActionEvent ev) {
		if(ev.getSource() instanceof ToggleButton) {
			ToggleButton tb = (ToggleButton)ev.getSource();
			
			if(tb.getId().equals("select"))
				currentTool = Tool.SELECT;
			else if(tb.getId().equals("paint"))
				currentTool = Tool.PAINT;
			else if(tb.getId().equals("delete"))
				currentTool = Tool.DELETE;
			
		}
	}
	
	@FXML
	private void initialize() {
		initGUI();
		refresh();
	}
	
	// *****************************************************************
	// GUI
	// *****************************************************************
	private TrackSegmentPaneListener segmentListener;
	
	private void initGUI() {
	
		segmentListener 		= new TrackSegmentPaneListener();
		currentPaintSegment 	= StraightHorizontal.class;
		currentTool 			= Tool.SELECT;
	}
	
	// *****************************************************************
	// RASTER
	// *****************************************************************
	private void generateRaster() {
			
		// clear
		fpContent.getChildren().clear();
		
		fpContent.setPrefWidth(currentTrackplan.getSize() * SEGMENTSIZE);
		fpContent.setPrefHeight(currentTrackplan.getSize() * SEGMENTSIZE);
		
		// COL, ROW
		List<Long[]> lstIgnore = new ArrayList<Long[]>();
		
		for(int row=0; row < currentTrackplan.getSize(); row++) {
			for(int col=0; col < currentTrackplan.getSize(); col++) {
				if(!listContaints(col, row, lstIgnore)) {
				
				TrackplanSegment  data 	  = findTrackSegmentData(row, col);
				BasicTrackSegment segment = null;
				
				if(data != null) {		
					try {
						Class<?> c = Class.forName(data.getType());
						segment = (BasicTrackSegment)utils.injectClass(c);
					} catch (ClassNotFoundException e) {
						e.printStackTrace();
					}
					
				}
				
				if(segment == null) {
					segment = new BasicTrackSegment();
				}	
				
				segment.setRow(row);
				segment.setColumn(col);
				segment.setSize(SEGMENTSIZE);
				segment.setSegmentData(data);
				segment.setOnMouseClicked(segmentListener);
				
				fpContent.getChildren().add(segment);
				
				
				// Ignores by overdimensional blocks
				if(segment instanceof BlockTrackSegment) {
					BlockTrackSegment bts = (BlockTrackSegment)segment;
					if(bts.getOrientation().equals(Orientation.HORIZONTAL)) {
						for(int i=0; i < bts.getBlockDimension() - 1; i++) {
							lstIgnore.add(new Long[] { col + (long)i + 1l, (long)row });
						}
					} else {
						for(int i=0; i < bts.getBlockDimension() - 1; i++) {
							lstIgnore.add(new Long[] { (long)col, row + (long)i + 1l });
						}
					}
				}
				
				}

			}
		}
	}
	
	private class TrackSegmentPaneListener implements EventHandler<MouseEvent> {
		public void handle(MouseEvent arg0) {
			onSegmentClicked(arg0, (BasicTrackSegment)arg0.getSource());
		}
	}
	
	private boolean listContaints(int col, int row, List<Long[]> lst) {
		for(Long[] a : lst) {
			if(a[0] == (long)col && a[1] == (long)row)
				return true;
		}
		return false;
	}
	// *****************************************************************
	// SEGMENT
	// *****************************************************************
	private void onSegmentClicked(MouseEvent event, BasicTrackSegment segment) {
		
		segment.onClicked(event);
		
		if(currentTool == Tool.DELETE) {
			BasicTrackSegment tsp = new BasicTrackSegment(); 
			tsp.setRow(segment.getRow());
			tsp.setColumn(segment.getColumn());
			tsp.setSize(SEGMENTSIZE);
			tsp.setOnMouseClicked(segmentListener);
			switchSegment(segment, tsp);
			
		} else if(currentTool == Tool.PAINT) {
			try {			
				if(currentPaintSegment == null)
					return;
				
				BasicTrackSegment bts = (BasicTrackSegment)utils.injectClass(currentPaintSegment);
				
				bts.setRow(segment.getRow());
				bts.setColumn(segment.getColumn());
				bts.setSize(segment.getSize());
				bts.setOnMouseClicked(segmentListener);
				
				switchSegment(segment, bts);
								
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}
	
	private void switchSegment(BasicTrackSegment oldSegment, BasicTrackSegment newSegment) {
		if(oldSegment.getClass().equals(newSegment.getClass()))
			return;
		
		Integer idx = fpContent.getChildren().indexOf(oldSegment);
		
		// If the old segment is oversized, we need to add some segments more
		if(oldSegment instanceof BlockTrackSegment) {
			BlockTrackSegment bts = (BlockTrackSegment)oldSegment;
			if(bts.getOrientation().equals(Orientation.HORIZONTAL)) {
				for(int i=0; i<bts.getBlockDimension() - 1; i++) {			
					BasicTrackSegment tsp = new BasicTrackSegment(); 
					tsp.setRow				(oldSegment.getRow());
					tsp.setColumn			(oldSegment.getColumn() + i + 1);
					tsp.setSize				(SEGMENTSIZE);
					tsp.setOnMouseClicked	(segmentListener);
					fpContent.getChildren().add(idx+i+1, tsp);
				}
			} else {
				// FIXME produce failures (Index calculation)
				for(int i=0; i<bts.getBlockDimension() - 1; i++) {			
					BasicTrackSegment tsp = new BasicTrackSegment(); 
					tsp.setRow				(oldSegment.getRow() + i + 1);
					tsp.setColumn			(oldSegment.getColumn());
					tsp.setSize				(SEGMENTSIZE);
					tsp.setOnMouseClicked	(segmentListener);
					fpContent.getChildren().add(idx+i+1, tsp);
				}
			}
		}
		
		fpContent.getChildren().remove(oldSegment);
		removeTrackSegmentData(oldSegment.getSegmentData());
		oldSegment = null;
		
		if(!newSegment.getClass().equals(BasicTrackSegment.class)) {
			addTrackSegmentData(newSegment.generateData(currentTrackplan));
		}
		
		fpContent.getChildren().add(idx, newSegment);
	}
	
	
	public void removeSegment(int col, int row) {
		
		List<Node> lstRemove = new ArrayList<Node>();
		for(Node n: fpContent.getChildren()) {
			BasicTrackSegment bts = (BasicTrackSegment)n;
			
			if(bts.getColumn().equals(col) && bts.getRow().equals(row)) {
				removeTrackSegmentData(bts.getSegmentData());
				lstRemove.add(bts);
			}
		}
		
		fpContent.getChildren().removeAll(lstRemove);
	}
	// *****************************************************************
	// DATA
	// *****************************************************************
	private Trackplan currentTrackplan;
	
	private TrackplanSegment findTrackSegmentData(int row, int column) {
		for(TrackplanSegment tps : currentTrackplan.getSegments()) {
			if(tps.getRow().equals(row) && tps.getCol().equals(column))
				return tps;
		}
		
		return null;
	}
	
	private void removeTrackSegmentData(TrackplanSegment tps) {
		if(tps != null) {
			currentTrackplan.getSegments().remove(tps);
			tpService.deleteTrackplanSegment(tps);
		}
	}
	
	private void addTrackSegmentData(TrackplanSegment tps) {
		currentTrackplan.getSegments().add(tps);
		tpService.saveTrackplanSegment(tps);
	}
	
	private void refresh() {
		currentTrackplan = tpService.getTrackplanByIdentifier("trackplan1");
		generateRaster();
	}
	
	private void save() {
		currentTrackplan = tpService.saveTrackplan(currentTrackplan);
	}
	
	
}
